package com.cdk8s.tkey.management.service;

import com.bcw.sso.model.dto.SsoClientDTO;
import com.bcw.sso.model.dto.SsoClientRedisDO;
import com.bcw.sso.model.pojo.SsoClient;
import com.cdk8s.tkey.management.constant.GlobalVariable;
import com.cdk8s.tkey.management.enums.DeleteEnum;
import com.cdk8s.tkey.management.enums.StateEnum;
import com.cdk8s.tkey.management.eventlistener.event.OauthClientRedisEvent;
import com.cdk8s.tkey.management.eventlistener.po.OauthClientRedisEventInfo;
import com.cdk8s.tkey.management.exception.OauthApiException;
import com.cdk8s.tkey.management.mapper.SsoClientMapper;
import com.cdk8s.tkey.management.mapstruct.OauthClientMapstruct;
import com.cdk8s.tkey.management.pojo.dto.param.SsoClientPageQueryParam;
import com.cdk8s.tkey.management.util.CollectionUtil;
import com.cdk8s.tkey.management.util.DatetimeUtil;
import com.cdk8s.tkey.management.util.JsonUtil;
import com.cdk8s.tkey.management.util.StringUtil;
import com.cdk8s.tkey.management.util.redis.StringRedisService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.HashSet;
import java.util.List;
import java.util.Set;


/**
 * @author ying
 */
@Service
@Slf4j
public class SsoClientService {

    private final SsoClientMapper ssoClientMapper;
    private final OauthClientMapstruct oauthClientMapstruct;
    private final StringRedisService<String, String> clientRedisService;
    private final ApplicationContext applicationContext;

    public SsoClientService(SsoClientMapper ssoClientMapper, OauthClientMapstruct oauthClientMapstruct, StringRedisService<String, String> clientRedisService, ApplicationContext applicationContext) {
        this.ssoClientMapper = ssoClientMapper;
        this.oauthClientMapstruct = oauthClientMapstruct;
        this.clientRedisService = clientRedisService;
        this.applicationContext = applicationContext;
    }

    //=====================================业务处理 start=====================================

    public SsoClientRedisDO findOneByClientId(String clientId) {
        String clientIdRedisKey = GlobalVariable.REDIS_CLIENT_ID_KEY_PREFIX + clientId;
        String result = clientRedisService.get(clientIdRedisKey);
        if (StringUtil.isBlank(result)) {
            return null;
        }
        return JsonUtil.toObject(result, SsoClientRedisDO.class);
    }

    public SsoClient findOneById(Long id) {
        return ssoClientMapper.selectByPrimaryKey(id);
    }

    public List<SsoClient> findListByIdList(List<Long> idList) {
        return ssoClientMapper.selectByIdIn(idList);
    }

    public PageInfo<SsoClientDTO> findPageByParam(SsoClientPageQueryParam param) {
        PageHelper.startPage(param.getPageNum(), param.getPageSize());
        List<SsoClient> oauthClientList = ssoClientMapper.selectByPageQueryParam(param);

        List<SsoClientDTO> oauthClientDTOList = oauthClientMapstruct.toDTOList(oauthClientList);
        return new PageInfo<>(oauthClientDTOList);
    }

    @Transactional(rollbackFor = RuntimeException.class)
    public void create(SsoClient oauthClient) {
        SsoClientRedisDO clientByClientId = findOneByClientId(oauthClient.getClientId());
        if (null != clientByClientId) {
            throw new OauthApiException("clientId 已存在");
        }

        initCreateBasicParam(oauthClient);
        ssoClientMapper.insert(oauthClient);

        OauthClientRedisEventInfo oauthClientRedisEventInfo = new OauthClientRedisEventInfo();
        oauthClientRedisEventInfo.setOauthClientListByCreate(Lists.newArrayList(oauthClient));
        applicationContext.publishEvent(new OauthClientRedisEvent(this, oauthClientRedisEventInfo));
    }

    @Transactional(rollbackFor = RuntimeException.class)
    public void update(SsoClient oauthClient) {
        SsoClientRedisDO clientByClientId = findOneByClientId(oauthClient.getClientId());
        if (null != clientByClientId && !clientByClientId.getId().equals(oauthClient.getId())) {
            throw new OauthApiException("clientId 已存在");
        }

        ssoClientMapper.updateByPrimaryKeySelective(oauthClient);

        /* 因为 client 已经变更，这里直接删除对应缓存的数据，以便下次有请求时，使用到最新的数据 */
        String clientIdRedisKey = GlobalVariable.REDIS_CLIENT_ID_KEY_PREFIX + oauthClient.getClientId();
        Boolean result = clientRedisService.delete(clientIdRedisKey);
        if (!result) {
            log.warn("SSO client info has changed! But the redis cache data failed to refresh(delete). Redis key: " + clientIdRedisKey);
        }

        OauthClientRedisEventInfo oauthClientRedisEventInfo = new OauthClientRedisEventInfo();
        oauthClientRedisEventInfo.setOauthClientListByCreate(Lists.newArrayList(oauthClient));
        applicationContext.publishEvent(new OauthClientRedisEvent(this, oauthClientRedisEventInfo));
    }

    @Transactional(rollbackFor = RuntimeException.class)
    public void batchDelete(List<Long> idList) {
        ssoClientMapper.updateDeleteEnumByIdList(DeleteEnum.DELETED.getCode(), idList);

        List<SsoClient> listByIdList = findListByIdList(idList);
        if (CollectionUtil.isEmpty(listByIdList)) {
            return;
        }

        delRedisKeys(listByIdList);

        OauthClientRedisEventInfo oauthClientRedisEventInfo = new OauthClientRedisEventInfo();
        oauthClientRedisEventInfo.setOauthClientListByDelete(listByIdList);
        applicationContext.publishEvent(new OauthClientRedisEvent(this, oauthClientRedisEventInfo));
    }

    private void delRedisKeys(List<SsoClient> ssoClientList) {
        if (!CollectionUtils.isEmpty(ssoClientList)) {
            Set<String> redisClientIds = new HashSet<>();
            ssoClientList.forEach(client -> {
                /* 因为 client 已经变更，这里直接删除对应缓存的数据，以便下次有请求时，使用到最新的数据 */
                String clientIdRedisKey = GlobalVariable.REDIS_CLIENT_ID_KEY_PREFIX + client.getClientId();

                redisClientIds.add(clientIdRedisKey);
            });

            try {
                clientRedisService.deleteByKeys(redisClientIds);
            } catch (Exception e) {
                log.warn("SSO client info has changed! But the redis cache data failed to refresh(delete). Redis keys: "
                        + JsonUtil.toJson(redisClientIds), e);
            }
        }
    }

    @Transactional(rollbackFor = RuntimeException.class)
    public void batchUpdateState(Integer stateEnum, List<Long> idList) {
        ssoClientMapper.updateStateEnumByIdList(stateEnum, idList);

        List<SsoClient> listByIdList = findListByIdList(idList);
        if (CollectionUtil.isEmpty(listByIdList)) {
            return;
        }

        delRedisKeys(listByIdList);

        OauthClientRedisEventInfo oauthClientRedisEventInfo = new OauthClientRedisEventInfo();
        if (StateEnum.ENABLE.getCode() == stateEnum) {
            oauthClientRedisEventInfo.setOauthClientListByCreate(listByIdList);
        } else {
            oauthClientRedisEventInfo.setOauthClientListByDelete(listByIdList);
        }

        applicationContext.publishEvent(new OauthClientRedisEvent(this, oauthClientRedisEventInfo));
    }

    public void initAllClientToRedis() {
        List<SsoClient> oauthClientList = ssoClientMapper.selectAll();
        OauthClientRedisEventInfo oauthClientRedisEventInfo = new OauthClientRedisEventInfo();
        oauthClientRedisEventInfo.setOauthClientListByCreate(oauthClientList);
        applicationContext.publishEvent(new OauthClientRedisEvent(this, oauthClientRedisEventInfo));
    }

    //=====================================业务处理  end=====================================

    //=====================================私有方法 start=====================================

    private void initCreateBasicParam(SsoClient oauthClient) {
        long currentEpochMilli = DatetimeUtil.currentEpochMilli();
//oauthClient.setId(GenerateIdUtil.getId());
        if (null == oauthClient.getStateEnum()) {
            oauthClient.setStateEnum(StateEnum.ENABLE.getCode());
        }
        oauthClient.setDeleteEnum(DeleteEnum.NOT_DELETE.getCode());
        oauthClient.setCreateDate(currentEpochMilli);
        oauthClient.setCreateUserId(currentEpochMilli);
        oauthClient.setUpdateDate(currentEpochMilli);
        oauthClient.setUpdateUserId(currentEpochMilli);
    }
    //=====================================私有方法  end=====================================

}
